using System;
using System.Collections.Generic;
using System.IO;

namespace InfiniTec.Threading
{
    /// <summary>
    /// This static class implements often used async operations. 
    /// </summary>
    public static class AsyncOperations
    {
        /// <summary>
        /// Returns an async operation that copies the contents of an input stream to an output stream.
        /// </summary>
        /// <param name="input">The stream to copy the data from.</param>
        /// <param name="output">The stream to copy the data to.</param>
        /// <param name="bytesToCopy">The number of bytes to copy.</param>
        /// <param name="bufferSize">The buffersize for each copy operation.</param>
        /// <returns></returns>
        public static AsyncOperation<long> GetCopyOperation(this Stream input, Stream output, long bytesToCopy, int bufferSize, object userState)
        {
        	if (input == null) throw new ArgumentNullException("input");
        	if (output == null) throw new ArgumentNullException("output");
			if (bytesToCopy < 0) throw new ArgumentOutOfRangeException("bytesToCopy", bytesToCopy, "bytesToCopy must be greater than or equal to zero");
			if (bufferSize < 0) throw new ArgumentOutOfRangeException("bufferSize", bytesToCopy, "bufferSize must be greater than or equal to zero");
			return new AsyncOperation<long>(CopyInternal(input, output, bytesToCopy, bufferSize), userState);
        }

    	/// <summary>
        /// Returns an async operation that copies the contents of an input stream to an output stream.
        /// </summary>
        /// <param name="input">The stream to copy the data from.</param>
        /// <param name="output">The stream to copy the data to.</param>
        /// <param name="bytesToCopy">The number of bytes to copy.</param>
        public static AsyncOperation<long> GetCopyOperation(Stream input, Stream output, long bytesToCopy, object userState)
        {
			if (input == null) throw new ArgumentNullException("input");
			if (output == null) throw new ArgumentNullException("output");
			if (bytesToCopy < 0) throw new ArgumentOutOfRangeException("bytesToCopy", bytesToCopy, "bytesToCopy must be greater than or equal to zero");
			return new AsyncOperation<long>(CopyInternal(input, output, bytesToCopy, 4096), userState);
        }

        /// <summary>
        /// Returns an async operation that copies the contents of an input stream to an output stream.
        /// </summary>
        /// <param name="input">The stream to copy the data from.</param>
        /// <param name="output">The stream to copy the data to.</param>
        public static AsyncOperation<long> GetCopyOperation(this Stream input, Stream output, object userState)
        {
			if (input == null) throw new ArgumentNullException("input");
			if (output == null) throw new ArgumentNullException("output");
			return new AsyncOperation<long>(CopyInternal(input, output, long.MaxValue, 4096), userState);
        }

        private static IEnumerable<OperationAction> CopyInternal(Stream input, Stream output, long bytesToCopy, int bufferSize)
        {
        	int count;

            long bytesCopied = 0;

            var buffer = new byte[Math.Min(bufferSize, bytesToCopy)];

            do
            {
				IAsyncResult ar = input.BeginRead(buffer, 0, buffer.Length, AsyncOperation.Current.CompleteAsyncOperation, null);

				if (!ar.CompletedSynchronously)
				{
					yield return OperationAction.WaitForAsyncCompletion();
				}

                count = input.EndRead(ar);

                ar = output.BeginWrite(buffer, 0, count, AsyncOperation.Current.CompleteAsyncOperation, null);

				if (!ar.CompletedSynchronously)
				{
					yield return OperationAction.WaitForAsyncCompletion();
				}

	            output.EndWrite(ar);

                bytesCopied += count;
            } while (count > 0 && bytesCopied < bytesToCopy);
        	yield return OperationAction.Return(bytesCopied);
        }

        public static AsyncOperation WrapOperation(string name, AsyncOperation operation, AsyncOperationTracker tracker, bool allowMultipleInvocations,
            object userState)
        {
            return new AsyncOperation(name, WrapInternal(operation), tracker, allowMultipleInvocations, userState);
        }

        public static AsyncOperation<T> WrapOperation<T>(string name, AsyncOperation<T> operation, AsyncOperationTracker tracker, bool allowMultipleInvocations,
            object userState)
        {
            return new AsyncOperation<T>(name, WrapInternal(operation), tracker, allowMultipleInvocations, userState);
        }

        private static IEnumerable<OperationAction> WrapInternal<T>(AsyncOperation<T> operation)
        {
            yield return new NestedOperation(operation);

            yield return new OperationResult<T>(operation.Result);
        }

        private static IEnumerable<OperationAction> WrapInternal(AsyncOperation operation)
        {
            yield return new NestedOperation(operation);
        }
    }
}
